iT邦幫忙

2026 iThome 鐵人賽

DAY 21
1

💡 今日學習目標:理解 SSRF 的攻擊原理與雲端環境下的致命後果,看懂為什麼「先解析 DNS 再檢查 IP」這個看起來很嚴謹的防線其實有漏洞,並掌握程式碼層、雲端層、網路層三道防守。


📌 前言:一個消失的類別

2021 年,SSRF 以 A10 的身分獨立成為一個類別,那次它是靠社群問卷投票空降進榜的,因為業界普遍認為它的危險程度被低估了。

四年後的 2025 年,你在榜單上找不到 SSRF 了

它沒有消失,而是被併進了 A01 存取控制失效CWE-918: Server-Side Request Forgery 現在被列在 A01 的重點 CWE 清單裡,和 CWE-200CWE-352 (CSRF) 並列。

OWASP 官方頁面沒有解釋合併的理由。所以以下是筆者的理解,你可以自己判斷合不合理:

存取控制失效的定義是「能做到超出被授權範圍的事」。而 SSRF 的本質是:伺服器被誘導去存取一個它「有能力存取、但這個請求不該存取」的資源

差別只在於越權的主體不是使用者本人,而是被當成代理人的伺服器。攻擊者自己碰不到內網,但你的伺服器碰得到,於是他借用你的手。

而從防守角度看,兩者的心法完全一致:不要讓使用者的輸入直接決定「要存取哪個資源」。這句話你在 Day 16 的 IDOR、Day 18 的排序欄位白名單都看過了,今天是同一句話的第三次出場。

理解這個歸類,比背下防禦手法更有價值。因為它告訴你:這三種看起來完全不同的漏洞,其實是同一個病。


🔍 危害場景:一個 SSRF,一億筆個資

先講一個真實案例,你就知道為什麼這個漏洞值得認真對待。

2019 年 3 月 22 至 23 日,美國 Capital One 銀行遭入侵,約 1.06 億筆信用卡申請人的個資外流(美國約 1 億筆、加拿大約 600 萬筆)。

特別注意這個時間差:入侵發生在 3 月,但 Capital One 一直到 7 月 17 日(被一位 GitHub 使用者通報後)才知道自己出事了。中間整整四個月,攻擊者來去自如。

攻擊路徑非常簡潔:

1. 攻擊者找到一台對外的 WAF 主機上的 SSRF 漏洞
2. 透過它去存取 http://169.254.169.254/latest/meta-data/iam/security-credentials/
   (AWS 的 Instance Metadata Service,每台 EC2 都能存取的內部服務)
3. 拿到了那台主機 IAM 角色的臨時憑證
4. 而那個 IAM 角色的權限開得太大,攻擊者用它列出並下載了 700 多個 S3 儲存桶

注意第 4 步:如果那個 IAM 角色遵守了最小權限原則(Day 14 SEI 法則 6),這起事件的規模會小非常多。SSRF 打開了門,但真正決定損失有多大的,是門後面那把鑰匙的權限。

📖 資料來源:美國司法部起訴書新聞稿(入侵日期與 S3 儲存桶數量出自此份起訴文件);Wiz Cloud Threat Landscape — Capital One Incident (March 2019)(攻擊鏈與時間軸整理)

🧭 這個案子明天還會再出現一次。 今天我們看的是「SSRF 這個漏洞本身怎麼被利用」;Day 22 會從架構的角度重看同一起事件,問一個不同的問題:為什麼一個漏洞可以被放大成一億筆?


🛠️ 脆弱的程式碼

// ❌ 錯誤範例:完全信任外部傳入的 URL
Function ProxyFetchImage(userProvidedUrl):
    Response = HttpClient.Get(userProvidedUrl)
    Return ImageResponse(Response.Bytes)

這種程式碼在真實專案裡到處都是:抓使用者頭像、產生網址預覽圖、呼叫 Webhook、匯入遠端檔案。功能都很正常,直到有人把 URL 換成內網位址。


⚠️ 為什麼「檢查字串」一定會失守

新手第一直覺是:那我把 127.0.0.1169.254.169.254 過濾掉不就好了?

以下每一個都會指向本機,而且都繞過了那個字串比對:

繞過手法 範例
十進位 IP http://2130706433/
八進位 / 十六進位 http://0177.0.0.1/http://0x7f000001/
省略寫法 http://127.1/http://0/
IPv6 環回與對映 http://[::1]/http://[::ffff:127.0.0.1]/
網址帳號欄位 http://允許的網域@169.254.169.254/
攻擊者自己的 DNS 註冊一個 A 記錄直接指向 169.254.169.254
302 轉址 一個通過檢查的網址,回應時把你導向內網

🔑 這張表想說的不是「你要記住這七種寫法」,而是:黑名單永遠列不完。這是 Day 13 SEI 法則講過的:驗證輸入要用白名單,不要用黑名單。你能想到的繞過方式,永遠比攻擊者少一種。

所以正確方向是:解析出真正的 IP,然後判斷它落在哪個網段。判斷「是不是私有網段」是一個封閉的、可窮舉的問題,而「有沒有漏掉哪種寫法」不是。


🚨 但是,連「解析 IP 再檢查」也有一個經典陷阱

網路上絕大多數的 SSRF 防禦範例,都是這樣寫的:

// ⚠️ 看起來很嚴謹,但有漏洞
targetIP = DNS.Resolve(parsedUrl.Host)      // 先解析
If IsPrivateOrInternalIP(targetIP):          // 再檢查
    Return Error(403)
Return HttpClient.Get(userProvidedUrl)       // 🚨 問題在這一行

看出來了嗎?你檢查的是 IP,但你最後拿去連線的是「網域」。

於是 HTTP 函式庫會自己再解析一次 DNS,而那是一次全新的查詢。

DNS Rebinding 的 TOCTOU 時序圖:檢查時解析到公網 IP,連線時解析到內網 IP

攻擊者只要把自己網域的 DNS 記錄 TTL 設成 0,就能讓這兩次查詢回傳不同的答案:檢查時給你一個乾淨的公網 IP,連線時換成 169.254.169.254。這就是 DNS Rebinding

🎯 這是典型的 TOCTOU(Time-of-Check to Time-of-Use)問題:檢查的那一刻與使用的那一刻之間,狀態被換掉了。

記住這個原則,它適用的範圍遠遠不只 SSRF:「你檢查的那個對象」與「你實際使用的那個對象」必須是同一個,否則檢查等於沒做。


✅ 正確的防守:三層一起上

第一層:程式碼

優先選項:如果你的需求允許,用網域白名單。

// ✅ 最佳解:目標是固定的幾個服務
ALLOWED_HOSTS = { "api.partner.com", "cdn.trusted.com" }
If NOT ALLOWED_HOSTS.Contains(parsedUrl.Host):
    Return Error(403)

很多團隊直接跳過這一步去做複雜的 IP 判斷,但其實大部分場景的目標網域是可以列舉的。能白名單就白名單,這是成本最低、最不會出錯的做法。

如果真的必須接受任意網址(例如網址預覽功能),那就要做完整版:

Function SafeFetch(userProvidedUrl):
    url = ParseUrl(userProvidedUrl)

    // 1. 協定白名單:只准 http/https
    //    否則 file:// 可以讀本機檔案、gopher:// 可以打內網 Redis
    If url.Scheme NOT IN ["http", "https"]:
        Return Error(403, "Protocol not allowed")

    // 2. 解析出所有 IP(一個網域可能有多筆 A 記錄,每一筆都要檢查)
    ips = DNS.ResolveAll(url.Host)
    For each ip in ips:
        If IsPrivateOrInternalIP(ip):
            Return Error(403, "Internal address blocked")

    // 3. 【關鍵】連線到「剛剛檢查過的那個 IP」,不要再交給函式庫解析
    Return HttpClient
        .ConnectTo(ips[0])                  // 直接指定 IP
        .WithHeader("Host", url.Host)       // 手動補回 Host 標頭
        .WithTlsServerName(url.Host)        // HTTPS 的 SNI 也要指定,否則憑證驗證會失敗
        .DisableRedirects()                 // 4. 不自動跟隨轉址
        .Get(url.Path)

IsPrivateOrInternalIP() 至少要涵蓋:127.0.0.0/810.0.0.0/8172.16.0.0/12192.168.0.0/16169.254.0.0/16(含 Metadata)、0.0.0.0/8,以及 IPv6 的 ::1fc00::/7fe80::/10

⚠️ 關於第 4 點的轉址:如果你的功能真的需要跟隨轉址,那就每一跳都要重新跑一次上面的檢查,不能只驗第一個網址。

第二層:雲端

Capital One 事件之後,AWS 推出了 IMDSv2 來從根本上防這一招。它的機制是:

# 第 1 步:用 PUT 取得一張 token(注意:是 PUT,而且要帶自訂標頭)
TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" \
     -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")

# 第 2 步:之後每次查詢都要帶著這張 token
curl -H "X-aws-ec2-metadata-token: $TOKEN" \
     http://169.254.169.254/latest/meta-data/

為什麼這樣就擋得住? 因為絕大多數 SSRF 只能讓伺服器發出簡單的 GET 請求,沒辦法指定 HTTP 方法、也沒辦法塞自訂標頭。IMDSv2 同時要求這兩件事,等於把最常見的 SSRF 利用鏈直接切斷。

另外兩個附加保護:預設的回應跳躍限制(hop limit)是 1,讓請求無法穿過容器或反向代理層抵達 IMDS;而且帶有 X-Forwarded-For 標頭的 PUT 請求會被直接拒絕,那個標頭正是反向代理會自動加上的。

⚠️ 但要注意:依 AWS 文件,預設是 IMDSv1 與 IMDSv2 兩者都接受。你必須主動把它設成 required,IMDSv1 才會真的關閉。這件事不會自己發生。GCP 與 Azure 也有對應的機制(要求帶 Metadata-FlavorMetadata 標頭),原理相同。

第三層:網路

最後一道,也是最不依賴開發者記得寫檢查的一道:出向流量限制(Egress Filtering)

把會對外抓取資料的服務放進一個獨立的子網路,在防火牆規則上直接禁止它連往 RFC1918 私有網段與 169.254.0.0/16。或者強制它所有的外連都走一台專用 Proxy,由 Proxy 統一做前面那些檢查。

🛡️ 這正是 Day 14 縱深防禦的實例:程式碼那層可能被新來的同事改壞、可能有你沒想到的繞過方式,但網路層的規則是跟程式碼無關的。三層獨立,攻擊者要三層都突破才會成功。


🎯 今日重點小結與防守心法

  • 🔹 心法 1:SSRF 是「伺服器版的越權存取」。理解它為什麼被併進 A01,你就會發現 IDOR、動態排序欄位、SSRF 的解法都是同一句話:不要讓使用者的輸入直接決定要存取哪個資源
  • 🔹 心法 2:檢查的對象與使用的對象,必須是同一個。TOCTOU 是這篇最值得帶走的觀念;檢查了網域卻連線到網域,等於什麼都沒檢查。
  • 🔹 心法 3:SSRF 打開的門有多大,取決於門後那把鑰匙。Capital One 損失一億筆資料,關鍵不只在那個 SSRF,更在那個權限開太大的 IAM 角色。最小權限原則,是所有漏洞的損害控制器。

🚀 階段三完結:從「製造」邁向「設計」

到今天為止,我們完成了 階段三:資安是製造出來的(Day 12 - Day 21)

這十天我們做了三件事:

  1. 建立了 SEI Secure Coding 十大防衛法則的思考框架(Day 12 - Day 14)。
  2. 縱覽 OWASP Top 10 從 2021 到 2025 的排名洗牌,理解每個名次變動背後的意義(Day 15)。
  3. 手把手演練了六種主流漏洞的防禦:A01 權限控制失效(IDOR)A04 加密機制失效A05 注入攻擊(SQLi)A02 安全設定缺陷(安全標頭)A07 認證失效(Session/JWT),以及今天的 SSRF

回頭看,你會發現這六篇的心法其實高度重疊:分離結構與資料、白名單而非黑名單、預設拒絕、最小權限。這不是巧合:十大類別是「症狀」的分類,而防守法則是「病因」的分類,所以少數幾條法則可以蓋掉大部分症狀。

接下來,我們即將進入:階段四 — 資安是「設計」出來的

階段三教的是「打字敲 Code 時把它寫對」。但有些問題,寫得再對也救不回來,因為問題出在架構決定的那一刻。

Day 22 開始,我們把視角從程式碼拉高到系統架構,進入威脅建模(STRIDE)的世界。


💬 明日預告:【Day 22】駭客只需要成功一次,我們必須每次都成功:安全架構設計思維
明天我們開啟階段四,談 Security by Design,以及一個很多系統都踩過的坑:當系統出錯時,它應該預設「放行」還是「拒絕」?


上一篇
【Day 20】【動手做】OWASP A07 認證失效:Session Cookie 與 JWT 防禦
下一篇
【Day 22】駭客只需要成功一次,我們必須每次都成功:安全架構設計思維
系列文
槍林彈雨下的資安防守:從品質觀念切入,帶開發者從零動手作資安 30 天22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
AndyAWD
iT邦新手 1 級 ‧ 2026-09-09 23:35:46

一億筆個資也太可怕

我要留言

立即登入留言